import { Request, Response } from 'express'
import dayjs from 'dayjs'
import lodash from 'lodash'
import { EExceptionStatusCode, IException } from '@/types/common'
import { EContainerServiceName } from '@/types/container'
import RequestLog from '@/models/mongo/RequestLog'
import { getContainer } from '@/container/utils'
import logger from './logger'
import { IS_DEV, RENDER_ERROR_STATUS_CODES } from '@/constants'

// 请求实例
export const request = <T = any>(): Request<Record<string, any>, any, T> => {
  const request = getContainer().resolve<Request>(EContainerServiceName.REQUEST)
  if (!request) {
    throw new Error('request instance is null')
  }
  return request
}

// 响应实例
export const response = (): Response => {
  const response = getContainer().resolve<Response>(EContainerServiceName.RESPONSE)
  if (!response) {
    throw new Error('response instance is null')
  }
  return response
}

// 从请求对象中获取参数
export const getParamsFromRequest = <T = Record<string, any>>(requestObj: Request): T => {
  // 获取url中的参数。如：/user/:id
  const urlParams = requestObj.params
  // 获取GET请求参数
  const queryParams = requestObj.query
  // 获取POST请求参数
  const bodyParams = requestObj.body
  let params: Request['body']
  switch (requestObj.method) {
    case 'GET':
      params = { ...urlParams, ...queryParams }
      break
    case 'POST':
    case 'PUT':
    case 'DELETE':
      params = { ...urlParams, ...bodyParams }
      break
    default:
      params = { ...urlParams, ...queryParams, ...bodyParams }
      break
  }
  return params as T
}

// 函数重载定义
export function params<T = any>(): T // 获取所有请求参数
export function params<T = any>(name: keyof T, req?: Request): T[keyof T] // 获取单个请求参数
export function params<T = any>(name: keyof T[], req?: Request): Partial<T> // 获取多个请求参数

// 根据请求的方式获取请求参数 GET/POST/PUT/DELETE 通用
export function params<T = Record<string, any>>(name?: keyof T | (keyof T)[], req?: Request): T[keyof T] | Partial<T> {
  const requestObj = req || request()
  const requestParams = getParamsFromRequest<T>(requestObj)
  if (name) {
    // 如果是数组则返回多个参数
    if (Array.isArray(name) && name.length > 0) {
      const result: Partial<T> = {}
      name.forEach(key => {
        result[key] = requestParams[key]
      })
      return result
    } else if (typeof name === 'string') {
      // 返回单个参数
      return requestParams[name]
    }
  }
  return requestParams
}

// 获取用户请求头部的Token
export const getUserToken = (req?: Request): string => {
  const requestObj = req || request()
  // 获取bearer token
  const bearerToken = requestObj.header('Authorization')
  // 如果存在bearer token则返回去除bearer的token
  if (bearerToken) {
    const jwtToken = bearerToken.split(' ')[1]
    if (jwtToken) {
      return jwtToken
    }
  }
  // 不存在则直接返回token
  return bearerToken || ''
}

// 覆盖用户请求头部的Token
export const replaceReqUserToken = (token: string, req?: Request) => {
  const requestObj = req || request()
  // 生成新的bearer token
  const newToken = 'Bearer ' + token
  // 覆盖原有的token
  requestObj.headers['authorization'] = newToken
  const rawPos = requestObj.rawHeaders.indexOf('Authorization')
  // 存在则替换，不存在则添加
  if (rawPos !== -1) {
    requestObj.rawHeaders[rawPos + 1] = newToken
  } else {
    requestObj.rawHeaders.push('Authorization', newToken)
  }
}

// 格式化异常堆栈信息
export const formatErrorStack = (stack?: string) => {
  const stacks: string[] = []
  // 开发环境下返回堆栈信息
  if (stack) {
    stack.split('\n').forEach(stack => stacks.push(stack.trim()))
  }
  return stacks
}

// 异常捕获处理
export const catchErrorHandler = (error: IException, response: Response) => {
  // 返回异常信息，没有code则返回500，开发环境返回堆栈信息
  const statusCode = error.statusCode || EExceptionStatusCode.SERVER_UNAVAILABLE
  // 开发环境下返回堆栈信息
  const stacks: string[] = IS_DEV ? formatErrorStack(error.stack) : []
  response.status(statusCode)
  // 需要渲染异常页面的状态码
  if (RENDER_ERROR_STATUS_CODES.includes(statusCode)) {
    // TODO 设置渲染引擎并渲染异常页面
    response.render('error', { name: error.name, message: error.message, statusCode, stacks, data: error.data })
  } else {
    response.send({ name: error.name, message: error.message, statusCode, stacks, data: error.data })
  }
  // 结束响应
  response.end()
}

// 根据请求信息生成日志数据
export const logDataByRequest = async (request: Request, response: Response) => {
  // 如果请求 未成功返回 则不记录日志
  let client_ip = request.ip
  if (!IS_DEV) {
    // TODO 获取真实客户端ip 待验证
    client_ip = request.header('x-forwarded-for') || request.socket.remoteAddress
  }
  const requestLog = new RequestLog()
  requestLog.user_id = request.user?.id ?? null
  requestLog.request_at = dayjs(request.requestTime).format('YYYY-MM-DD HH:mm:ss')
  requestLog.method = request.method
  requestLog.origin = (request.header('host') || request.hostname) + request.originalUrl // 请求来源
  requestLog.route = request.baseUrl + (request.path === '/' ? '' : request.path || '') // 路由路径
  requestLog.client_ip = client_ip
  requestLog.duration = Date.now() - request.requestTime!
  requestLog.req_headers = request.headers
  requestLog.req_params = getParamsFromRequest(request)
  requestLog.res_status = response.statusCode
  requestLog.res_headers = response.getHeaders()
  requestLog.res_body = response.locals.cacheResBody
  requestLog.res_cookies = response.get('Set-Cookie')
  logger.info(
    '请求信息：',
    `【客户端IP】${requestLog.client_ip}`,
    `【请求来源】${requestLog.origin}`,
    `【访问路由】${requestLog.route}`,
    `【请求方式】${requestLog.method}`,
    `【请求用户】${requestLog.user_id}`,
    `【客户端信息】${request.headers['user-agent']}`
  )
  // 存在请求参数则记录请求参数
  if (Object.keys(requestLog.req_params).length > 0) {
    logger.info('请求参数：', requestLog.req_params)
  }
  // 记录响应信息
  logger.info('响应信息：', `【响应状态】${requestLog.res_status}`, `【响应时间】${requestLog.duration}ms`)
  // 存在异常信息则记录异常信息
  if (request.errorInfo) {
    requestLog.has_error = true
    requestLog.error_info = {
      name: request.errorInfo?.name,
      message: request.errorInfo?.message,
      stacks: formatErrorStack(request.errorInfo?.stack) // 格式化异常堆栈信息
    }
  } else {
    // 没有异常信息则记录响应数据
    logger.info(
      '响应数据：',
      typeof requestLog.res_body === 'object' ? JSON.stringify(requestLog.res_body) : requestLog.res_body
    )
  }
  // 去除异常信息和请求耗时
  lodash.unset(request, ['errorInfo', 'duration'])
  try {
    await requestLog.save()
  } catch (e) {
    console.error('记录请求日志失败：', e)
    throw e
  }
}

// 通用成功返回Txt函数
export const successTxt = (message: string) => {
  response().status(200).send(message)
}

// 通用成功返回Json中携带data的函数
export const successData = <T = any>(data: { message?: string; data?: T }) => {
  response().status(200).json(data)
}

// 通用成功返回Json中只携带message的函数
export const successMessage = (message: string) => {
  successData({ message })
}

// 通用成功返回Json中只携带list的函数
export const successList = <T = any>(
  data: { list: T[]; total?: number; current?: number; pageSize?: number },
  message?: string
) => {
  successData<typeof data>({ data, message })
}

// 通用失败返回函数
export const failData = <T = any>(data: { message?: string; data?: T }) => {
  response().status(202).json(data)
}
